/******************************************************************************************************
Copyright (C) 2013 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals DBPlugin, Settings, GroupBox, LineEdit, SpinBox, DoubleSpinBox, CheckBox, ToolButton, Dialog, qsTr, settings, MessageBox, ComboBox, Application, print, ProgressDialog, Dir, File, TextStream, serialization, MnUi*/
/*jslint plusplus: true, indent: 4*/

function dbAssignmentsToPredictionDB() {

	"use strict";

	var i,
		progress,
		countRecords,
		startTime,
		dir,
		file,
		stream,
		cidSrc,
		startTimeRecord,
		dw,
		foundASV = false,
		oldSilentMode,
		oldFileHistory,
		options,
		recordFrom,
		recordTo,
		update1H,
		update13C,
		update15N,
		update17O,
		update19F,
		update29Si,
		update31P,
		destinationDB,
		ASVFilter,
		scoreFrom,
		scoreTo,
		queryString,
		hits,
		molItem,
		srcClient = DBPlugin.createClient();

	function getUserOptions() {

		var dialog,
			srcHostSettingsKey = "Database/Export Assignments To Prediction/Host",
			srcPortSettingsKey = "Database/Export Assignments To Prediction/Port",
			srcUserSettingsKey = "Database/Export Assignments To Prediction/User",
			srcPasswordSettingsKey = "Database/Export Assignments To Prediction/Password",
			srcSavePasswordSettingsKey = "Database/Export Assignments To Prediction/Save Password",
			srcDatabaseSettingsKey = "Database/Export Assignments To Prediction/SourceDatabase",
			dstDatabaseKey = "Database/Export Assignments To Prediction/DestinationDatabase",
			dst1HKey = "Database/Export Assignments To Prediction/Update 1H Prediction DB",
			dst13CKey = "Database/Export Assignments To Prediction/Update 13C Prediction DB",
			dst15NKey = "Database/Export Assignments To Prediction/Update 15N Prediction DB",
			dst17OKey = "Database/Export Assignments To Prediction/Update 17O Prediction DB",
			dst19FKey = "Database/Export Assignments To Prediction/Update 19F Prediction DB",
			dst29SiKey = "Database/Export Assignments To Prediction/Update 29Si Prediction DB",
			dst31PKey = "Database/Export Assignments To Prediction/Update 31P Prediction DB",
			dstFilterByASVScoreKey = "Database/Export Assignments To Prediction/Filter Results By ASV Score",
			dstASVScoreFromKey = "Database/Export Assignments To Prediction/ASV Score From",
			dstASVScoreToKey = "Database/Export Assignments To Prediction/ASV Score To",
			defaultConnectionSettings = new Settings("DBConnection/"),
			defHost = defaultConnectionSettings.value("Server", "localhost"),
			defPort = defaultConnectionSettings.value("Port", "5504"),
			defUser = defaultConnectionSettings.value("Username", ""),
			defPassword = defaultConnectionSettings.value("Password", ""),
			gbSrc,
			gbDst,
			gbSrcConnection,
			leSrcHost,
			leSrcPort,
			leSrcUser,
			leSrcPassword,
			chSrcSavePassword,
			tbSrcConnect,
			gbSrcDatabase,
			cbSrcDatabase,
			gbSrcRecords,
			sbFrom,
			sbTo,
			gbDstDatabase,
			cmbDB,
			N,
			Z,
			values = [],
			gbDstOptions,
			gbDstASVScore,
			cbDstASVScore,
			dsbFrom,
			dsbTo,
			gbDstNucleids,
			chDst1H,
			chDst13C,
			chDst15N,
			chDst17O,
			chDst19F,
			chDst29Si,
			chDst31P,
			ok,
			txtRecords,
			txtSrcHost;

		function createGroupBox(aParent, aTitle) {
			var gb = new GroupBox();
			gb.title = aTitle;
			if (aParent !== null) {
				aParent.add(gb);
			}
			return gb;
		}

		function createLineEdit(aParent, aLabel, aText) {
			var le = new LineEdit();
			le.label = aLabel;
			le.text = aText;
			if (aParent !== null) {
				aParent.add(le);
			}
			return le;
		}

		function createSpinBox(aParent, aLabel) {
			var sb = new SpinBox();
			if (aParent !== null) {
				aParent.add(aLabel, sb);
			}
			return sb;
		}

		function createDoubleSpinBox(aParent, aLabel) {
			var dsb = new DoubleSpinBox();
			if (aParent !== null) {
				aParent.add(aLabel, dsb);
			}
			return dsb;
		}

		function createCheckBox(aParent, aText, aChecked) {
			var cb = new CheckBox();
			cb.text = aText;
			cb.checked = aChecked;
			if (aParent !== null) {
				aParent.add(cb);
			}
			return cb;
		}

		function createToolButton(aParent, aText) {
			var tb = new ToolButton();
			tb.text = aText;
			if (aParent !== null) {
				aParent.add(tb);
			}
			return tb;
		}

		function populateComboBox(aComboBox, aValues, aPreselected) {
			var i;
			aComboBox.clear();
			if (aPreselected === "") {
				aComboBox.addItem("");
			}
			for (i = 0; i < aValues.length; i++) {
				aComboBox.addItem(aValues[i]);
				if (aValues[i] === aPreselected) {
					aComboBox.currentIndex = i;
				}
			}
		}

		function allNucleidsUnchecked() {
			return (!(chDst1H.checked || chDst13C.checked || chDst15N.checked || chDst17O.checked || chDst19F.checked || chDst29Si.checked || chDst31P.checked));
		}

		dialog = new Dialog(qsTr("Export Assignments To Prediction DB"));
		dialog.onSrcConnectClicked = function () {
			if (tbSrcConnect.text === "Connect") {
				tbSrcConnect.enabled = false;
				srcClient.connect(leSrcHost.text, leSrcPort.text, leSrcUser.text, leSrcPassword.text);
				if (srcClient.connected()) {
					leSrcHost.enabled = false;
					leSrcPort.enabled = false;
					leSrcUser.enabled = false;
					leSrcPassword.enabled = false;
					tbSrcConnect.text = "Disconnect";
					populateComboBox(cbSrcDatabase, srcClient.databases(), settings.value(srcDatabaseSettingsKey, ""));
					gbSrcDatabase.enabled = true;
				} else {
					MessageBox.critical(qsTr("Could not connect to server."));
				}
				tbSrcConnect.enabled = true;
			} else if (tbSrcConnect.text === "Disconnect") {
				srcClient.disconnect();
				leSrcHost.enabled = true;
				leSrcPort.enabled = true;
				leSrcUser.enabled = true;
				leSrcPassword.enabled = true;
				tbSrcConnect.text = "Connect";
				gbSrcDatabase.enabled = false;
			}
		};

		dialog.onSrcDatabaseChanged = function (aDatabase) {
			if (aDatabase) {
				var db = srcClient.definition(aDatabase);
				if (db.records > 0) {
					gbSrcRecords.enabled = true;
					sbFrom.minimum = 1;
					sbFrom.maximum = db.records;
					sbFrom.value = 1;
					sbTo.minimum = 1;
					sbTo.maximum = db.records;
					sbTo.value = db.records;
				} else {
					gbSrcRecords.enabled = false;
				}
			} else {
				gbSrcRecords.enabled = false;
			}
			if (!gbSrcRecords.enabled) {
				sbFrom.minimum = 0;
				sbFrom.value = 0;
				sbTo.minimum = 0;
				sbTo.value = 0;
			}
		};

		gbSrc = createGroupBox(null, qsTr("Source"));
		gbDst = createGroupBox(null, qsTr("Destination"));

		dialog.add(gbSrc, gbDst);

		gbSrcConnection = createGroupBox(gbSrc, qsTr("Connection"));
		gbSrcConnection.add("");
		leSrcHost = createLineEdit(gbSrcConnection, qsTr("Host:\t"), settings.value(srcHostSettingsKey, defHost));
		leSrcPort = createLineEdit(gbSrcConnection, qsTr("Port:\t"), settings.value(srcPortSettingsKey, defPort));
		leSrcUser = createLineEdit(gbSrcConnection, qsTr("User:\t"), settings.value(srcUserSettingsKey, defUser));
		leSrcPassword = createLineEdit(gbSrcConnection, qsTr("Password:\t"), settings.value(srcPasswordSettingsKey, defPassword));
		leSrcPassword.echoMode = LineEdit.EchoMode.Password;
		chSrcSavePassword = createCheckBox(null, qsTr("Save Password"), settings.value(srcSavePasswordSettingsKey, false));
		tbSrcConnect = createToolButton(null, qsTr("Connect"));
		tbSrcConnect.clicked.connect(dialog, dialog.onSrcConnectClicked);
		gbSrcConnection.add(chSrcSavePassword, tbSrcConnect);

		gbSrcDatabase = createGroupBox(gbSrc, qsTr("Database"));
		gbSrcDatabase.enabled = false;
		cbSrcDatabase = new ComboBox();
		cbSrcDatabase.editable = false;
		cbSrcDatabase['currentIndexChanged(QString)'].connect(dialog, dialog.onSrcDatabaseChanged);
		gbSrcDatabase.add(qsTr("Database:\t"), cbSrcDatabase);

		gbSrcRecords = createGroupBox(gbSrc, qsTr("Records"));
		gbSrcRecords.enabled = false;
		sbFrom = createSpinBox(gbSrcRecords, qsTr("From:"));
		sbTo = createSpinBox(gbSrcRecords, qsTr("To:"));

		gbDstDatabase = createGroupBox(gbDst, qsTr("Database"));
		cmbDB = new ComboBox();

		N = 78;
		Z = 90;

		values = [];
		for (i = N; i <= Z; i++) {
			values.push(String.fromCharCode(i));
		}
		populateComboBox(cmbDB, values, settings.value(dstDatabaseKey, "N"));
		cmbDB.editable = false;

		gbDstDatabase.add("Existing:\t\t\t", cmbDB);
		cmbDB.currentText = settings.value(dstDatabaseKey, "N");

		gbDstOptions = createGroupBox(gbDst, qsTr("Saving Options"));

		if (Application.ASV) {
			try {
				Application.ASV.verify();
			} catch (error) {
				if (!error.license) {
					foundASV = true;
				}
			}
		}

		if (foundASV) {
			gbDstASVScore = createGroupBox(gbDstOptions, qsTr("Verify Score Range"));
			cbDstASVScore = createCheckBox(gbDstASVScore, qsTr("Store Only Score Values in the Range"), settings.value(dstFilterByASVScoreKey, false));
			dsbFrom = createDoubleSpinBox(gbDstASVScore, qsTr("From:"));
			dsbTo = createDoubleSpinBox(gbDstASVScore, qsTr("To:"));
			dsbFrom.minimum = -1;
			dsbFrom.maximum = 1;
			dsbFrom.singleStep = 0.1;
			dsbFrom.value = settings.value(dstASVScoreFromKey, 0);
			dsbTo.minimum = -1;
			dsbTo.maximum = 1;
			dsbTo.singleStep = 0.1;
			dsbTo.value = settings.value(dstASVScoreToKey, 0);
		}

		gbDstNucleids = createGroupBox(gbDstOptions, qsTr("Prediction DB's to Update"));
		chDst1H = createCheckBox(gbDstNucleids, "1H", settings.value(dst1HKey, true));
		chDst13C = createCheckBox(gbDstNucleids, "13C", settings.value(dst13CKey, true));
		chDst15N = createCheckBox(gbDstNucleids, "15N", settings.value(dst15NKey, false));
		chDst17O = createCheckBox(gbDstNucleids, "17O", settings.value(dst17OKey, false));
		chDst19F = createCheckBox(gbDstNucleids, "19F", settings.value(dst19FKey, false));
		chDst29Si = createCheckBox(gbDstNucleids, "29Si", settings.value(dst29SiKey, false));
		chDst31P = createCheckBox(gbDstNucleids, "31P", settings.value(dst31PKey, false));

		ok = false;
		while (!ok && dialog.exec()) {
			if (!srcClient.connected()) {
				MessageBox.critical(qsTr("Source client must be connected to proceed."));
				tbSrcConnect.setFocus();
			} else if (cbSrcDatabase.currentText.length <= 0) {
				MessageBox.critical(qsTr("You must specify a source database."));
				cbSrcDatabase.setFocus();
			} else if (!srcClient.openDatabase(cbSrcDatabase.currentText)) {
				MessageBox.critical(qsTr("Could not open source database '{0}'.").format(cbSrcDatabase.currentText));
				cbSrcDatabase.setFocus();
			} else if (!srcClient.openDatabase(cbSrcDatabase.currentText)) {
				MessageBox.critical(qsTr("Could not open source database '{0}'.").format(cbSrcDatabase.currentText));
				cbSrcDatabase.setFocus();
			} else if (srcClient.definition(srcClient.database).records <= 0) {
				MessageBox.critical(qsTr("Source database {0} is empty.").format(srcClient.database));
				cbSrcDatabase.setFocus();
			} else if (sbFrom.value > sbTo.value) {
				MessageBox.critical(qsTr("To Record must be greater than or equal to From Record."));
				sbFrom.setFocus();
			} else if (allNucleidsUnchecked()) {
				MessageBox.critical(qsTr("Some nuclide must be chosen."));
				chDst1H.setFocus();
			} else if (foundASV) {
				if (dsbFrom.value > dsbTo.value) {
					MessageBox.critical(qsTr("'To Score' must be greater than or equal to 'From Score'."));
					dsbFrom.setFocus();
				} else {
					ok = true;
				}
			} else {
				ok = true;
			}
			txtRecords = sbTo.value - sbFrom.value > 1 ? "records " + sbFrom.value + ".." + sbTo.value : "record " + sbFrom.value;
			txtSrcHost = " at " + leSrcHost.text + ":" + leSrcPort.text;
			ok = ok && Application.mainWindow.waitForInput(qsTr("Export {0}\nfrom {1}{2}\nto NMRPredictor Database?\n").format(txtRecords, srcClient.database, txtSrcHost));
		}
		if (!ok) {
			return undefined;
		}

		// Save settings
		settings.setValue(srcHostSettingsKey, leSrcHost.text);
		settings.setValue(srcPortSettingsKey, leSrcPort.text);
		settings.setValue(srcUserSettingsKey, leSrcUser.text);
		settings.setValue(srcPasswordSettingsKey, chSrcSavePassword.checked ? leSrcPassword.text : "");
		settings.setValue(srcSavePasswordSettingsKey, chSrcSavePassword.checked);
		settings.setValue(srcDatabaseSettingsKey, srcClient.database);
		settings.setValue(dstDatabaseKey, cmbDB.currentText);
		settings.setValue(dst1HKey, chDst1H.checked);
		settings.setValue(dst13CKey, chDst13C.checked);
		settings.setValue(dst15NKey, chDst15N.checked);
		settings.setValue(dst17OKey, chDst17O.checked);
		settings.setValue(dst19FKey, chDst19F.checked);
		settings.setValue(dst29SiKey, chDst29Si.checked);
		settings.setValue(dst31PKey, chDst31P.checked);

		if (foundASV) {
			settings.setValue(dstFilterByASVScoreKey, cbDstASVScore.checked);
			settings.setValue(dstASVScoreFromKey, dsbFrom.value);
			settings.setValue(dstASVScoreToKey, dsbTo.value);
		}
		options = {
			destinationDB: cmbDB.currentText,
			recordFrom: sbFrom.value,
			recordTo: sbTo.value,
			update1H: chDst1H.checked,
			update13C: chDst13C.checked,
			update15N: chDst15N.checked,
			update17O: chDst17O.checked,
			update19F: chDst19F.checked,
			update29Si: chDst29Si.checked,
			update31P:  chDst31P.checked
		};
		if (foundASV) {
			options.ASVFilter = cbDstASVScore.checked;
			options.scoreFrom = dsbFrom.value;
			options.scoreTo = dsbTo.value;
		} else {
			options.ASVFilter = false;
		}
		return options;
	}

	function asvScoreInUserDefinedRange(itemId, hits) {
		var i = 0;

		while (i < hits.count) {
			if (itemId === hits.at(i).recordId()) {
				return true;
			}
			i++;
		}
		return false;
	}

	function updateDB() {

		var exitStatus;
		print("Database " + destinationDB + " selected");
		if (update1H) {
			print("Updating 1H...");

			exitStatus = Application.NMRPredictor.updateUserDB("1H", molItem,  destinationDB);
			if (exitStatus === 0) {
				print("Updated succesfully");
			} else {
				print("Error updating database. Code: " + exitStatus);
			}
		}
		if (update13C) {
			print("Updating 13C..");
			exitStatus = Application.NMRPredictor.updateUserDB("13C", molItem,  destinationDB);
			if (exitStatus === 0) {
				print("Updated succesfully");
			} else {
				print("Error updating database. Code: " + exitStatus);
			}
		}
		if (update15N) {
			print("Updating 15N...");
			exitStatus = Application.NMRPredictor.updateUserDB("15N", molItem,  destinationDB);
			if (exitStatus === 0) {
				print("Updated succesfully");
			} else {
				print("Error updating database. Code: " + exitStatus);
			}
		}
		if (update17O) {
			print("Updating 17O...");
			exitStatus = Application.NMRPredictor.updateUserDB("17O", molItem,  destinationDB);
			if (exitStatus === 0) {
				print("Updated succesfully");
			} else {
				print("Error updating database. Code: " + exitStatus);
			}
		}
		if (update19F) {
			print("Updating 19F...");
			exitStatus = Application.NMRPredictor.updateUserDB("19F", molItem,  destinationDB);
			if (exitStatus === 0) {
				print("Updated succesfully");
			} else {
				print("Error updating database. Code: " + exitStatus);
			}
		}
		if (update29Si) {
			print("Updating 29Si...");
			exitStatus = Application.NMRPredictor.updateUserDB("29Si", molItem,  destinationDB);
			if (exitStatus === 0) {
				print("Updated succesfully");
			} else {
				print("Error updating database. Code: " + exitStatus);
			}
		}
		if (update31P) {
			print("Updating 31P...");
			exitStatus = Application.NMRPredictor.updateUserDB("31P", molItem,  destinationDB);
			if (exitStatus === 0) {
				print("Updated succesfully");
			} else {
				print("Error updating database. Code: " + exitStatus);
			}
		}
	}

	options = getUserOptions();

	if (options) {
		destinationDB = options.destinationDB;
		recordFrom = options.recordFrom;
		recordTo = options.recordTo;
		update1H = options.update1H;
		update13C = options.update13C;
		update15N = options.update15N;
		update17O = options.update17O;
		update19F = options.update19F;
		update29Si = options.update29Si;
		update31P = options.update31P;

		if (options.ASVFilter) {
			ASVFilter = true;
			scoreFrom = options.scoreFrom;
			scoreTo = options.scoreTo;
		} else {
			ASVFilter = false;
		}

		progress = new ProgressDialog();
		progress.setRange(recordFrom, recordTo + 1);
		progress.show();

		countRecords = 0;
		startTime = new Date();
		print("It is now " + startTime.toString());
		try {
			dir = new Dir(Dir.temp());
			file = new File(dir.filePath("temp.mol"));
			stream = new TextStream(file);
			oldSilentMode = Application.NMRPredictor.silentMode;
			Application.NMRPredictor.silentMode = true;
			oldFileHistory = serialization.fileHistory;
			serialization.fileHistory = false;
			if (ASVFilter) {
				queryString = scoreFrom + " < " + scoreTo;
				hits = DBPlugin.search(srcClient, queryString, "NUMERIC", "", "Verification Result");
			}

			for (cidSrc = recordFrom; cidSrc <= recordTo && !progress.wasCanceled; ++cidSrc) {
				startTimeRecord = new Date();
				progress.value = cidSrc;
				progress.labelText = qsTr("Exporting assignments from record {0} ({1} of {2})...").format(cidSrc, cidSrc - recordFrom + 1, recordTo - recordFrom + 1);
				Application.mainWindow.processEvents();
				dw = Application.mainWindow.newWindow();
				print("Retrieving record " + cidSrc + "...");

				if (ASVFilter) {
					if (asvScoreInUserDefinedRange(cidSrc, hits)) {
						print("Result in selected range");
						DBPlugin.getRecord(srcClient, cidSrc);
					} else {
						print("Verification result not in selected range. Record discarded");
					}
				} else {
					DBPlugin.getRecord(srcClient, cidSrc);
				}

				molItem = Application.mainWindow.activeDocument.item(0, "Molecule");
				if (molItem.isValid()) {
					updateDB();
					countRecords++;
				} else {
					print("No assignments found. Record discarded");
				}

				dw.close();
			}
		} finally {
			progress.close();
			file.remove();
			Application.NMRPredictor.silentMode = oldSilentMode;
			serialization.fileHistory = oldFileHistory;
		}
		print("All done");
		srcClient.disconnect();
		MessageBox.information(qsTr("Exported {0} records.").format(countRecords));
	}
}

if (this.MnUi && MnUi.scripts_db) {
	MnUi.scripts_db.scripts_db_ExportAssignmentsToPredictionDB = dbAssignmentsToPredictionDB;
}
